#include "AudioManager.h"
#include  <iostream>
#include "fmod_errors.h"
#include "LogUtil.h"
#include <math.h>

AudioManager* AudioManager::instance = NULL;
unsigned int AudioManager::dspBufferLength = 1024;
int AudioManager::dspNumBuffers = 4;

Audio::Audio(){
	sound = NULL;
	freePlay = NULL;
	length = 0;
	volume = 1.0f;
}

Audio::~Audio(){
	if(freePlay != NULL){
		freePlay->stop();
	}
	if(sound != NULL){
        sound->release();
        sound = NULL;
    }
}

void Audio::init(){
	if(sound){
		unsigned int len;
		sound->getLength(&len, FMOD_TIMEUNIT_PCM);
		int pro;
		sound->getDefaults(&orgFrequency, &pro);
		length = len * 1000.0/ orgFrequency;
	}
}

void Audio::play(){
	if(freePlay){
		seek(0);
        freePlay->setPaused(false);
	}else{
		LogUtil::getInstance()->log("Audio", "play: NULL");
	}
}

void Audio::pause(){
	if(freePlay){
		freePlay->setPaused(true);
	}else{
		LogUtil::getInstance()->log("Audio", "pause: NULL");
	}
}

void Audio::stop() {
	if (freePlay != nullptr) {
		freePlay->stop();
		freePlay = nullptr;
	}
}

void Audio::toggle(){
	if(freePlay){
		bool playing = false;
		freePlay->isPlaying(&playing);
		if(playing){
			pause();
		}else{
			play();
		}
	}
}

double Audio::getTime(){
	if(freePlay){
        unsigned int pos=0;
        freePlay->getPosition(&pos, FMOD_TIMEUNIT_PCM);
        return pos * 1000.0/ orgFrequency;
    }
	return 0;
}

double Audio::getLength(){
	return length;
}

double Audio::getSampleRate() {
	return orgFrequency;
}

void Audio::seek(double position){
	if(freePlay){
        freePlay->setPosition((unsigned int)(position * orgFrequency), FMOD_TIMEUNIT_PCM);
	}else{
		LogUtil::getInstance()->log("Audio", "seek: NULL");
	}
}

void Audio::setVolume(float vol){
	volume = vol;
	if(freePlay != NULL){
		freePlay->setVolume(volume);
	}
}

TrackCell::TrackCell(){
	audio = NULL;
	channel = NULL;
	volume = 1.0f;
	offset = 0;
}

void TrackCell::stop() {
	if (channel != nullptr) {
		channel->stop();
		channel = nullptr;
	}
}

Track::Track(){
	system = nullptr;
	group = NULL;
	cellCount = 0;
	length = 0;
	sampleRate = 44100;
	speed = 1.0f;
	startDspClock = 0;
	bufSize = AudioManager::dspBufferLength * AudioManager::dspNumBuffers;
	prevBufPos = 0;
}

Track::~Track(){
	if(group){
		group->stop();
		group->release();
	}
	for(auto cell: cells){
		delete cell;
	}
}

void Track::clear() {
	if (group != nullptr) {
		group->stop();
	}
	for (auto cell : cells) {
		delete cell;
	}
	cells.clear();
	cellCount = 0;
}

void Track::init(){
	if (group != nullptr) {
		group->setPaused(true);
	}
	unsigned long long dc, dcp;
	group->getDSPClock(&dc, &dcp);
	startDspClock = dc;
}

void Track::update() {
	if (group == nullptr) {
		return;
	}

	unsigned long long dc, dcp;
	group->getDSPClock(&dc, &dcp);
	int currPos = static_cast<int>((long long)dc - startDspClock);
	if (currPos >= length * sampleRate / 1000.0) {
		group->setPaused(true);
		group->stop();
		return;
	}
	int currBufPos = currPos;
	if (isPlaying()) {
		currBufPos += bufSize;
	}
	if (currBufPos <= prevBufPos) {
		return;
	}

	for (TrackCell * cell : cells) {
		if (cell != nullptr && cell->offset > prevBufPos && cell->offset <= currBufPos) {
			if (cell->channel != nullptr) {
				LogUtil::getInstance()->log("Audio", "Why cell %d playing ;w;", cell->offset);
				cell->stop();
			}
			LogUtil::getInstance()->log("Audio", "Play sound %d in update", cell->offset);
			FMOD_RESULT result = system->playSound(cell->audio->sound, group, true, &cell->channel);
			if (result != FMOD_OK) {
				LogUtil::getInstance()->error("Audio", "Failed to play sound in update: %s", FMOD_ErrorString(result));
				cell->channel = nullptr;
				return;
			}
			cell->channel->setVolume(cell->volume);
			cell->channel->setDelay(startDspClock + cell->offset, 0, false);
			cell->channel->setPaused(false);
		}
	}

	prevBufPos = currBufPos;
}

void Track::updateCell(TrackCell * cell) {
	cell->stop();
	int pos = prevBufPos - cell->offset;
	LogUtil::getInstance()->log("Audio", "updateCell: pos:%d offset:%d len:%.3f", pos, cell->offset, cell->audio->getLength());
	if (pos >= 0 && pos < (int)(cell->audio->getLength() * sampleRate / 1000.0)) {
		LogUtil::getInstance()->log("Audio", "Play sound %d in updateCell", cell->offset);
		if(cell->channel){
			cell->channel->stop();
			cell->channel = nullptr;
		}
		FMOD_RESULT result = system->playSound(cell->audio->sound, group, true, &cell->channel);
		if (result != FMOD_OK) {
			LogUtil::getInstance()->error("Audio", "Failed to play sound in updateCell: %s", FMOD_ErrorString(result));
			cell->channel = nullptr;
			return;
		}
		cell->channel->setVolume(cell->volume);
		cell->channel->setPosition((int)(pos * cell->audio->getSampleRate() / sampleRate), FMOD_TIMEUNIT_PCM);
		cell->channel->setDelay(startDspClock + prevBufPos, 0, false);
		cell->channel->setPaused(false);
	}
}

/**
从头开始播放
*/
void Track::play(){
	if(group){
		//stop状态, 开始播放
		seek(0);
		resume();
	}
}

void Track::pause(){
	if(group){
		group->setPaused(true);
	}
}

void Track::resume(){
	if(getTime() >= length){
		return;
	}
	if(group){
		update();
		group->setPaused(false);
	}
}

void Track::toggle(){
	if(group){
		if(isPlaying()){
			pause();
		}else{
			resume();
		}
	}
}

bool Track::isPlaying() {
	if (group == nullptr) {
		return false;
	}
	bool paused = true;
	group->getPaused(&paused);
	return !paused;
}

void Track::seek(double position){
	if(!group){
		return;
	}

	bool playing = isPlaying();
	pause();

	unsigned long long dc, dcp;
	group->getDSPClock(&dc, &dcp);
	LogUtil::getInstance()->log("Audio", "Seek %llu %.3f, %u cells, %u empty places"
		, dc, position, cellCount, (unsigned)(cells.size() - cellCount));
	int currPos = (int)(position * sampleRate / 1000.0);
	startDspClock = (long long)dc - currPos;
	prevBufPos = currPos;
	for (TrackCell * cell : cells) {
		if (cell != nullptr) {
			updateCell(cell);
		}
	}

	if (playing) {
		resume();
	}
}

void Track::setVolume(float volume){
	if(group){
		group->setVolume(volume);
	}
}

double Track::getTime(){
	if (group == nullptr) {
		return 0;
	}
	unsigned long long dc, dcp;
	group->getDSPClock(&dc, &dcp);
	return ((long long)dc - startDspClock) * 1000.0 / sampleRate;
}

double Track::getLength(){
	return length;
}

double Track::getBufTime(){
	return prevBufPos * 1000.0 / sampleRate;
}

//重置长度, 这个是虚拟的时长, 跟实际播放什么没关系
void Track::setLength(double len){
	LogUtil::getInstance()->log("Audio", "change length from %.3f to %.3f", length, len);
	length = len;
}

TrackCell * Track::getCell(unsigned int tid) {
	if (tid >= cells.size()) {
		return nullptr;
	}
	return cells[tid];
}

bool Track::isCellValid(unsigned int tid) {
	return getCell(tid) != nullptr;
}

int Track::addCell(Audio *audio){
	if(audio->sound == NULL){
		return -1;
	}

	TrackCell* cell = new TrackCell;
	cell->audio = audio;
	cell->channel = nullptr;
	cell->offset = INT_MAX;
	cells.push_back(cell);
	cellCount++;
	return cells.size() - 1;
}

void Track::removeCell(unsigned int tid) {
	TrackCell * cell = getCell(tid);
	if (cell == nullptr) {
		return;
	}
	cells[tid] = nullptr;
	cellCount--;
	if(cell->channel){
		cell->channel->stop();
		cell->channel->setDelay(0, 0, false);
	}
	cell->stop();
	delete cell;
}

//调整audio在mainTrack上的播放偏移
void Track::setCellOffset(unsigned int tid, double time){
	TrackCell * cell = getCell(tid);
	if (cell == nullptr) {
		return;
	}
	cell->offset = (int)(time * sampleRate / 1000.0);
	updateCell(cell);
}

void Track::setCellVolume(unsigned int tid, float volume) {
	TrackCell * cell = getCell(tid);
	if (cell == nullptr) {
		return;
	}
	cell->volume = volume;
	if (cell->channel != nullptr) {
		cell->channel->setVolume(volume);
	}
}


void Track::setSpeed(float speed){
	if (speed != speed) {
		speed = 1.0f;
	} else if(speed < 0.5f){
		speed = 0.5f;
	}else if(speed > 1.5f){
		speed = 1.5f;
	}
	this->speed = speed;

	FMOD::DSP *olddsp;
	group->getDSP(0, &olddsp);
	if(olddsp){
		group->removeDSP(olddsp);
	}

	if (fabs(speed - 1.0f) < 1e-6f) {
		group->setPitch(1.0f);
		return;
	}

	FMOD::DSP* dsp;
	if(system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp) != FMOD_OK){
		LogUtil::getInstance()->error("Audio", "Failed to create dsp");
		return;
	}
	dsp->setParameterFloat(0, 1/speed);
	group->addDSP(0, dsp);
	group->setPitch(speed);
}


AudioManager::AudioManager()
{
    FMOD_RESULT result;

    if((result = System_Create(&system)) != FMOD_OK){
        LogUtil::getInstance()->error("Audio", "Failed to create FMOD system: %s", FMOD_ErrorString(result));
    }
    system->setDSPBufferSize(dspBufferLength, dspNumBuffers);
    system->setSoftwareChannels(256);

	sampleRate = 0;
	if ((result = system->getDriverInfo(0, 0, 0, 0, &sampleRate, 0, 0)) != FMOD_OK) {
		sampleRate = 44100;
	}
	if(sampleRate > 48000){
		sampleRate = 44100;
	}

	system->setSoftwareFormat(sampleRate, FMOD_SPEAKERMODE_STEREO, 0);
    if ((result = system->init(128, FMOD_INIT_NORMAL, 0)) != FMOD_OK) {
        LogUtil::getInstance()->error("Audio", "Failed to initialize FMOD system: %s", FMOD_ErrorString(result));
		return;
    }
}

AudioManager::~AudioManager(){
	for(Audio* audio: samples){
		if(audio != NULL){
			delete audio;
		}
	}
	samples.clear();
	for (Track * track : tracks) {
		delete track;
	}
	tracks.clear();
    system->release();
    system = NULL;
}

AudioManager* AudioManager::getInstance(){
    if(instance == NULL)
        instance = new AudioManager;
    return instance;
}

bool AudioManager::releaseInstance(){
	if(instance!=NULL){
		delete instance;
		instance = NULL;
		return true;
	}return false;
}

void AudioManager::update(){
    system->update();
    for (Track * track : tracks) {
    	if (track != nullptr) {
    		track->update();
    	}
    }
}

int AudioManager::load(const char *filename){
	auto find = audioBuffers.find(filename);
	if(find != audioBuffers.end()){
		return find->second.audioId;
	}
    FMOD_RESULT result;
	FMOD::Sound* sound;
	size_t size = 0;
	char* buffer = nullptr;

	AudioRawBuffer b;
	b.audioId = -1;
	do{
		std::ifstream fin;
		fin.open(Widen(filename), std::ios::binary);

		if(!fin.is_open()){
			break;
		}

		fin.seekg(0, std::ios::end);
		size_t sz = fin.tellg();
		fin.seekg(0, std::ios::beg);

		if(sz == 0){
			fin.close();
			break;
		}

		buffer = new char[sz+1];
		size = sz;

		buffer[sz]=0;
		fin.read(buffer,sz);

		fin.close();

		b.buffer = buffer;
		b.size = sz;
	}while(0);

	// unreleaseStreamBuffer.insert(make_pair(filename,b));
	if(buffer==nullptr || size==0){
		audioBuffers.insert(std::make_pair(filename, b));
		return -1;
	}

	FMOD_CREATESOUNDEXINFO info;
	memset(&info,0,sizeof(info));
	info.length = size;
	info.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
    if ((result = system->createSound(buffer, FMOD_OPENMEMORY|FMOD_ACCURATETIME|FMOD_IGNORETAGS, &info, &sound)) != FMOD_OK) {
    	//重试一次
    	if((result = system->createSound(buffer, FMOD_OPENMEMORY|FMOD_ACCURATETIME|FMOD_IGNORETAGS|FMOD_MPEGSEARCH, &info, &sound)) != FMOD_OK){
			sound = NULL;
			LogUtil::getInstance()->error("Audio", "Failed to load \"%s\": %s", filename, FMOD_ErrorString(result));
			return -1;
    	}
    }
    Audio* audio = new Audio;
	audio->sound = sound;
	audio->init();
	samples.push_back(audio);
	b.audioId = samples.size() - 1;
	audioBuffers.insert(std::make_pair(filename, b));
	return b.audioId;
}

void AudioManager::release(unsigned int index){
	Audio* audio = getAudio(index);
	if(audio != NULL){
		delete audio;
	}
	for(auto it = audioBuffers.begin(); it != audioBuffers.end(); ++it){
		if(it->second.audioId == index){
			if(it->second.size != 0){
				delete[] it->second.buffer;
			}
			audioBuffers.erase(it);
			break;
		}
	}
	samples[index] = NULL;
}

Audio* AudioManager::getAudio(unsigned int index){
	if(index >= samples.size()){
		return NULL;
	}
	return samples[index];
}

void AudioManager::prepare(unsigned int index){
	Audio* audio = getAudio(index);
	if(audio == NULL){
		return;
	}
	if(audio->freePlay != NULL){
		return;
	}
	FMOD::Channel* channel;
	FMOD_RESULT result = system->playSound(audio->sound, 0, true, &channel);
	if (result != FMOD_OK) {
		LogUtil::getInstance()->error("Audio", "Failed to play sound");
		return;
	}
	channel->setVolume(audio->volume);
	audio->freePlay = channel;
}

int AudioManager::createTrack() {
	Track * track = new Track;
	if(system->createChannelGroup(NULL, &track->group) != FMOD_OK){
		LogUtil::getInstance()->error("Audio", "Failed to initialize track");
		return -1;
	}
	track->init();
	track->system = system;
	track->sampleRate = sampleRate;
	tracks.push_back(track);
	return tracks.size() - 1;
}

void AudioManager::releaseTrack(unsigned int index) {
	Track * track = getTrack(index);
	tracks[index] = nullptr;
	delete track;
}

Track * AudioManager::getTrack(unsigned int index){
	if (index >= tracks.size()) {
		return NULL;
	}
	return tracks[index];
}

double AudioManager::getDSPDelay() {
	return dspBufferLength * dspNumBuffers * 1000.0 / sampleRate;
}
